﻿<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>O.T.O.G.</title>
    <!--<script language="javascript" src="jquery-1.3.2-vsdoc2.js" type="text/javascript"></script>-->
    <script language="javascript" type="text/javascript">
        var interpretable = true;
        // TODO: Must a "map" be declared this way?
        var labelMapping = {};
        var logBuffer = "";
        var logType = {Error: 0,
            Message: 1,
            Warning: 2};
        // TODO: Allow more than 32 vars/type?
        var MAXVARS = 1<<5;
        var sentences = new Array();
        var sentenceType = {AddSentence: 0,
            SubSentence: 1,
            ConditionalSentence: 2,
            SkipSentence: 3};
        var vars = {x: new Array(MAXVARS),
            y: 0,
            z: new Array(MAXVARS)};
        
        for (var i = 0; i < vars.x.length; vars.x[i++] = 0);
        for (var i = 0; i < vars.z.length; vars.z[i++] = 0);

        function interpret() {
            log(logType.Message, "Lexical-syntactic analysis... ");
            lexicalSyntacticAnalysis();
            log(logType.Message, "Done!\n");
            log(logType.Message, "Semantic analysis... ");
            semanticAnalysis();
            log(logType.Message, "Done!\n");
            
            if (!interpretable)
                return;
            
            var pc = 0;
            
            while (true) {
                if (pc >= sentences.length)
                    break;
                
                var s = sentences[pc];
                
                switch (s.type) {
                    case sentenceType.AddSentence:
                    case sentenceType.SubSentence:
                        modifyVar(s.type, s.leftOperand, s.leftOperandIndex);
                        pc++;
                        break;
                    case sentenceType.ConditionalSentence:
                        pc = solveJump(s.operand, s.operandIndex, s.destiny, pc);
                        break;
                    case sentenceType.SkipSentence:
                        pc++;
                        break;
                    default:
                        break;
                }
            }
            
            alert("{X: {" + vars.x.toString() + "}, Y: " + vars.y + ", Z: {" + vars.z.toString() + "}}\n" + logBuffer);
        }
        
        function lexicalSyntacticAnalysis() {
            var lines = document.getElementById("input").value.split('\n'); //$("#input").val().split('\n');
            
            for (var i = 0; i < lines.length; i++) {
                var line = lines[i];
                
                // Skip comment lines
                if (line[0] == ';') continue;
                
                var matches;

                if ((matches = line.match(/^(\[([A-Z]([1-9][0-9]*)?)\] )?([A-Z]([1-9][0-9]*)?) \<\- ([A-Z]([1-9][0-9]*)?) (\+|\-) 1( ;.*)?$/)) != null)
                    pushAddSubSentence(i + 1, matches[2], matches[4][0], matches[5], matches[6][0], matches[7], matches[8] == '+');
                else if ((matches = line.match(/^(\[([A-Z]([1-9][0-9]*)?)\] )?IF ([A-Z]([1-9][0-9]*)?) \/\= 0 GOTO ([A-Z]([1-9][0-9]*)?)( ;.*)?$/)) != null)
                    pushConditionalSentence(i + 1, matches[2], matches[4][0], matches[5], matches[6]);
                else if ((matches = line.match(/^(\[([A-Z]([1-9][0-9]*)?)\] )?([A-Z]([1-9][0-9]*)?) \<\- ([A-Z]([1-9][0-9]*)?)( ;.*)?$/)) != null)
                    pushSkipSentence(i + 1, matches[2], matches[4][0], matches[5], matches[6][0], matches[7]);
                else
                    // FIXME: Use log() instead
                    alert("Error!");
                
                // FIXME: Hackish :-(
                if (matches[2] != null)
                    labelMapping[matches[2]] = i + 1;
            }
        }
        
        function log(type, message, line) {
            if (line != null)
                logBuffer += "[" + line + "] ";
            
            switch (type) {
                case logType.Error:
                    logBuffer += "Error: ";
                    break;
                case logType.Warning:
                    logBuffer += "Warning: ";
                    break;
                default:
                    break;
            }
            
            logBuffer += message + '\n';
        }
        
        function modifyVar(type, operand, operandIndex) {
            var oi = operandIndex - 1;
            
            switch (operand) {
                case 'X':
                    vars.x[oi] = type == sentenceType.AddSentence ? vars.x[oi] + 1 : vars.x[oi] - 1;
                    break;
                case 'Y':
                    vars.y = type == sentenceType.AddSentence ? vars.y + 1 : vars.y - 1;
                    break;
                case 'Z':
                    vars.z[oi] = type == sentenceType.AddSentence ? vars.z[oi] + 1 : vars.z[oi] - 1;
                    break;
                default:
                    break;
            }
        }
        
        function pushAddSubSentence(line, label, leftOperand, leftOperandIndex, rightOperand, rightOperandIndex, isAdd) {
            sentences.push({line: line,
                type: isAdd ? sentenceType.AddSentence : sentenceType.SubSentence,
                label: label,
                leftOperand: leftOperand,
                leftOperandIndex: leftOperandIndex,
                rightOperand: rightOperand,
                rightOperandIndex: rightOperandIndex});
        }
        
        function pushConditionalSentence(line, label, operand, operandIndex, destiny) {
            sentences.push({line: line,
                type: sentenceType.ConditionalSentence,
                label: label,
                operand: operand,
                operandIndex: operandIndex,
                destiny: destiny});
        }
        
        // This function could be mixed with Add/Sub one, checking whether isAdd is null
        function pushSkipSentence(line, label, leftOperand, leftOperandIndex, rightOperand, rightOperandIndex) {
            sentences.push({line: line,
                type: sentenceType.SkipSentence,
                label: label,
                leftOperand: leftOperand,
                leftOperandIndex: leftOperandIndex,
                rightOperand: rightOperand,
                rightOperandIndex: rightOperandIndex});
        }
        
        // TODO:
        // Check whether operands are inside MAXVARS (error)
        // Check whether labels are declared anywhere (warning, as I think GOTO allows this)
        // Check whether Y var contains no index (error) Should this be checked even before?
        // Check whether vars are X, Y or Z (error). Should this be checked even before?
        function semanticAnalysis() {
            for (var i = 0; i < sentences.length; i++) {
                var s = sentences[i];
                
                switch (s.type) {
                    case sentenceType.AddSentence:
                    case sentenceType.SubSentence:
                    case sentenceType.SkipSentence:
                        if (s.leftOperand != s.rightOperand) {
                            interpretable = false;
                            log(logType.Error, "Left and right operands don't match", s.line);
                            continue;
                        }
                    case sentenceType.ConditionalSentence:
                        break;
                    default:
                        break;
                }
            }
        }
        
        function solveJump(operand, operandIndex, destiny, pc) {
            var nextPC, oi = operandIndex - 1;
            
            // FIXME: Hackish fix
            if (destiny == 'E')
                return sentences.length;
            
            switch (operand) {
                case 'X':
                    nextPC = vars.x[oi] != 0 ? labelMapping[destiny] - 1 : pc + 1;
                    break;
                case 'Y':
                    nextPC = vars.y != 0 ? labelMapping[destiny] - 1 : pc + 1;
                    break;
                case 'Z':
                    nextPC = vars.z[oi] != 0 ? labelMapping[destiny] - 1 : pc + 1;
                    break;
                default:
                    break;
            }
            
            return nextPC;
        }
    </script>
</head>
<body>
    <h1>O.T.O.G.</h1>
    <!--O.nly T.echies O.wn G.OTO-->
    <input id="btnRun" onclick="javascript: interpret();" type="button" value="Run" /><br />
    <textarea id="input" cols="30" rows="10">
Z1 <- Z1 + 1
Z1 <- Z1 + 1
Z1 <- Z1 + 1
Y <- Y + 1
[A1] IF Z1 /= 0 GOTO A2
Z2 <- Z2 + 1
IF Z2 /= 0 GOTO E
[A2] Y <- Y + 1
Y <- Y + 1
Z1 <- Z1 - 1
Z2 <- Z2 + 1
IF Z2 /= 0 GOTO A1
; f(Z1) = 2 * Z1 + 1 :-)</textarea>
    <div id="divLog"></div>
</body>
</html>
